/*
 * Copyright 2009 Original Author(s)
 * 
 * This file is part of Kommando
 * 
 * Kommando is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Kommando is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Kommando.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kommando.core.selector;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.JPopupMenu;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

import org.kommando.core.HistoryTracker;
import org.kommando.core.KommandoWindow;
import org.kommando.core.catalog.Catalog;
import org.kommando.core.catalog.CatalogObject;
import org.kommando.core.catalog.TextObject;
import org.kommando.core.search.DefaultResult;
import org.kommando.core.search.Result;

/**
 * 
 * 
 * @author Peter De Bruycker
 */
public abstract class CatalogObjectSelector extends AbstractSelector<CatalogObject> {

    private Deque<SelectorState<CatalogObject>> stateStack = new ArrayDeque<SelectorState<CatalogObject>>();
    private JTextComponent textComponent;
    private boolean inTextMode;
    private Catalog catalog;
    private HistoryTracker historyTracker;

    public CatalogObjectSelector(CommandSelector commandSelector, KommandoWindow.Selector selector, Catalog catalog, HistoryTracker historyTracker) {
        super(commandSelector, selector);
        this.catalog = catalog;
        this.historyTracker = historyTracker;
    }

    public void setInTextMode(boolean inTextMode) {
        this.inTextMode = inTextMode;

        getWindow().setInTextMode(getSelector(), inTextMode);

        if (inTextMode) {
            Result<CatalogObject> selection = getState().getSelection();
            if (selection != null) {
                String text = selection.getName();
                // TODO is this even necessary?
                if (selection.getValue().hasExtension(String.class)) {
                    text = selection.getValue().getExtension(String.class);
                }
                getTextComponent().setText(text);
            } else {
                getTextComponent().setText("");
            }

            // TODO is this the correct place to do this?
            getTextComponent().requestFocusInWindow();

            updateText();
        }
    }

    protected HistoryTracker getHistoryTracker() {
        return historyTracker;
    }

    private void updateText() {
        TextObject textObject = new TextObject(textComponent.getText());
        catalog.enhance(textObject);

        Result<CatalogObject> result = new DefaultResult<CatalogObject>(textObject);

        SelectorState<CatalogObject> state = new SelectorState<CatalogObject>(textObject);
        state.setSelection(result);
        state.setResults(Collections.singletonList(result));
        setState(state);
    }

    public boolean isInTextMode() {
        return inTextMode;
    }

    @Override
    public boolean handleKeyEvent(CommandSelector commandSelector, KeyEvent e) {
        if (inTextMode) {
            // TODO tab should move focus to next selector
            // TODO enter should exit textmode?
            if (e.getID() == KeyEvent.KEY_RELEASED && e.getKeyCode() == KeyEvent.VK_ESCAPE) {

            }

            if (e.getID() == KeyEvent.KEY_RELEASED) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    setInTextMode(false);
                    return true;
                }

                if (e.getKeyCode() == KeyEvent.VK_TAB && e.getModifiers() == 0) {
                    setInTextMode(false);
                    commandSelector.nextSelector();
                    return true;
                }
            }

            textComponent.dispatchEvent(e);
            return true;
        } else {
            if (e.getID() == KeyEvent.KEY_RELEASED) {
                if (!inTextMode && e.getKeyChar() == '\'') {
                    setInTextMode(true);
                    return true;
                }

                // key bindings from quicksilver:
                if (e.getKeyChar() == '/' || e.getKeyCode() == KeyEvent.VK_RIGHT) {
                    this.goDownInSelectionList();
                    return true;
                }
                if (e.getKeyChar() == '?' || e.getKeyCode() == KeyEvent.VK_LEFT) {
                    this.goUpInSelectionList();
                    return true;
                }

                if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                    if (!stateStack.isEmpty()) {
                        SelectorState<CatalogObject> state = stateStack.getFirst();
                        stateStack.clear();
                        setState(state);

                        queryChanged("");

                        return true;
                    }
                }
            }

            return super.handleKeyEvent(commandSelector, e);
        }
    }

    @Override
    public void prepareForDisplay() {
        textComponent.setText("");
        stateStack.clear();

        super.prepareForDisplay();
    }

    public void goDownInSelectionList() {
        CatalogObject selection = getInternalSelection();
        if (selection == null || !catalog.hasChildren(selection)) {
            return;
        }

        stateStack.push(getState());

        List<Result<CatalogObject>> results = new ArrayList<Result<CatalogObject>>();
        List<CatalogObject> children = catalog.getChildren(selection);

        for (CatalogObject child : children) {
            results.add(new DefaultResult<CatalogObject>(child));
        }

        SelectorState<CatalogObject> newState = new SelectorState<CatalogObject>(children);
        newState.setResults(results);
        if (!results.isEmpty()) {
            newState.setSelection(results.get(0));
        }
        setState(newState);
    }

    public void goUpInSelectionList() {
        if (stateStack.isEmpty()) {
            return;
        }

        setState(stateStack.pop());
    }

    public void setTextComponent(final JTextComponent textComponent) {
        this.textComponent = textComponent;

        // TODO remove the documentlistener from the previous text component
        textComponent.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void removeUpdate(DocumentEvent e) {
                updateText();
            }

            @Override
            public void insertUpdate(DocumentEvent e) {
                updateText();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                updateText();
            }
        });

        getWindow().setTextComponent(getSelector(), textComponent);
    }

    public JTextComponent getTextComponent() {
        return textComponent;
    }

    @Override
    protected SelectorState<CatalogObject> getInitialState() {
        return new SelectorState<CatalogObject>(catalog.getObjects());
    }

    @Override
    protected JPopupMenu createPopupMenu(final CatalogObject object) {
        JPopupMenu popupMenu = new JPopupMenu();

        popupMenu.add(new AbstractAction("Debug info") {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Id: " + object.getId());
                System.out.println("Extensions: ");
                for (Map.Entry<Class<?>, Object> extensionEntry : object.getExtensions().entrySet()) {
                    System.out.println("- " + extensionEntry.getKey() + ": " + extensionEntry.getValue());
                }
            }
        });

        return popupMenu;

    }
}
